import {Component, OnInit, Input, AfterViewInit} from '@angular/core';

import {Map} from 'ol';
import View from 'ol/View';
import {Image as ImageLayer, Tile as TileLayer} from 'ol/layer';
import BingMaps from 'ol/source/BingMaps';
import RasterSource from 'ol/source/Raster';
import {unByKey} from 'ol/Observable';
import * as _ from 'lodash';
import {fromLonLat, transform} from 'ol/proj';
import {UtilComponent} from '../../interface/util-component.interface';

declare var d3: any;

@Component({
    selector: 'kylin-raster',
    templateUrl: './raster.component.html',
    styleUrls: ['./raster.component.less']
})
export class RasterComponent implements OnInit, AfterViewInit, UtilComponent {
    // ---------- 接口实现 begin -------------- //
    hasContent = true;
    map: Map;

    @Input() set data(val) {
        Object.assign(this, val);
    };

    // ---------- 接口实现 end ---------------- //{
    raster: any;
    private minVgi = 0;
    private maxVgi = 0.25;
    private bins = 10;
    private timer = null;
    private barWidth = 15;
    private plotHeight = 150;
    private chart: any;
    private chartRect: any;
    private tip: any;
    private bar: any;

    currentLayers = {};

    constructor() {
    }

    ngOnInit() {
    }

    ngAfterViewInit() {
        this.chart = d3.select('#plot').append('svg').attr('width', this.barWidth * this.bins).attr('height', this.plotHeight);
        this.chartRect = this.chart.node().getBoundingClientRect();
        this.tip = d3.select(document.body).append('div').attr('class', 'tip');
    }

    add() {
        const bing = new BingMaps({
            key: 'AslSLNN27EriTtgRbJTJHZYZhNEhIQygwI76cYNMuyiXI8vTd9apf7PFFWePMAP9',
            imagerySet: 'Aerial'
        });

        this.raster = new RasterSource({
            sources: [bing],
            operation: (pixels, data) => {
                const pixel = pixels[0];
                const value = this.vgi(pixel);
                this.summarize(value, data['counts']);
                if (value >= data['threshold']) {
                    pixel[0] = 0;
                    pixel[1] = 255;
                    pixel[2] = 0;
                    pixel[3] = 128;
                } else {
                    pixel[3] = 0;
                }
                return pixel;
            },
            lib: {
                vgi: this.vgi,
                summarize: this.summarize
            }
        });

        this.raster.set('threshold', 0.1);

        this.raster.on('beforeoperations', (event) => {
            event.data.counts = this.createCounts(this.minVgi, this.maxVgi, this.bins);
            event.data.threshold = this.raster.get('threshold');
        });

        this.raster.on('afteroperations', (event) => {
            this.schedulePlot(event.resolution, event.data.counts, event.data.threshold);
        });

        this.currentLayers['tileLayer'] = new TileLayer({
            source: bing
        })

        this.currentLayers['imageLayer'] = new ImageLayer({
            source: this.raster
        })

        this.map.addLayer(this.currentLayers['tileLayer']);
        this.map.addLayer(this.currentLayers['imageLayer']);
        this.map.getView().animate({center: [-9651695, 4937351]}, {zoom: 13});
    }

    cancel(options?: any): any {
        _.forIn(this.currentLayers, (value) => {
            this.map.removeLayer(value);
        })
        this.currentLayers = {};
        this.map.getView().animate({center: fromLonLat([108.943904, 34.319323])}, {zoom: 5});
    }

    private vgi = (pixel) => {
        const r = pixel[0] / 255;
        const g = pixel[1] / 255;
        const b = pixel[2] / 255;
        return (2 * g - r - b) / (2 * g + r + b);
    }

    private summarize = (value, counts) => {
        const min = counts.min;
        const max = counts.max;
        const num = counts.values.length;
        if (value < min) {
            // do nothing
        } else if (value >= max) {
            counts.values[num - 1] += 1;
        } else {
            const index = Math.floor((value - min) / counts.delta);
            counts.values[index] += 1;
        }
    }

    private createCounts = (min, max, num) => {
        const values = new Array(num);
        for (let i = 0; i < num; ++i) {
            values[i] = 0;
        }
        return {
            min: min,
            max: max,
            values: values,
            delta: (max - min) / num
        };
    }

    private schedulePlot = (resolution, counts, threshold) => {
        if (this.timer) {
            clearTimeout(this.timer);
            this.timer = null;
        }
        // this.timer = setTimeout(this.plot.bind(null, resolution, counts, threshold), 1000 / 60);
        this.timer = setTimeout(() => {
            this.plot(resolution, counts, threshold);
        }, 1000 / 60);
    }

    private plot = (resolution, counts, threshold) => {
        const yScale = d3.scaleLinear()
            .domain([0, d3.max(counts.values)])
            .range([0, this.plotHeight]);

        this.bar = this.chart.selectAll('rect').data(counts.values);

        this.bar.enter().append('rect');

        this.bar.attr('class', (count, index) => {

            const value = counts.min + (index * counts.delta);
            return 'bar' + (value >= threshold ? ' selected' : '');
        }).attr('width', this.barWidth - 2);

        this.bar.transition().attr('transform', (value, index) => {
            return 'translate(' + (index * this.barWidth) + ', ' +
                (this.plotHeight - yScale(value)) + ')';
        })
            .attr('height', yScale);

        this.bar.on('mousemove', (count, index) => {
            const threshold = counts.min + (index * counts.delta);
            if (this.raster.get('threshold') !== threshold) {
                this.raster.set('threshold', threshold);
                this.raster.changed();
            }
        });

        this.bar.on('mouseover', (count, index) => {
            let area = 0;
            for (let i = counts.values.length - 1; i >= index; --i) {
                area += resolution * resolution * counts.values[i];
            }
            this.tip.html(this.message(counts.min + (index * counts.delta), area));
            this.tip.style('display', 'block');
            this.tip.transition().style({
                left: (this.chartRect.left + (index * this.barWidth) + (this.barWidth / 2)) + 'px',
                top: (d3.event.y - 60) + 'px',
                opacity: 1
            });
        });

        this.bar.on('mouseout', () => {
            this.tip.transition().style('opacity', 0).each('end', () => {
                this.tip.style('display', 'none');
            });
        });
    }

    private message = (value, area) => {
        const acres = (area / 4046.86).toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
        return acres + ' acres at<br>' + value.toFixed(2) + ' VGI or above';
    }


}
